#include <stdio.h>
#include <netdb.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
#include <ctype.h>
#include <iptables.h>
#include "ipt_ZeSA.h"

static void help(void)
{
	printf(
"ZeSA match v%s options:\n"
"--port       protocol port      Match protocol port\n"
"--match-cmds       cmd1[...[[ cmd2] cmd3]...]\n"
"                                Match ftp or http protocol's command\n"
"                        HTTP: CONNECT DELETE GET HEAD LINK OPTIONS PATCH POST \n"
"                              PUT TRACE UNLINK CONNECT\n"
"                         FTP: ABOR ACCT ADAT ALLO APPE AUTH CCC CDUP CONF CWD \n"
"                              DELE ENC EPRT EPSV FEAT HELP LANG LIST LPRT LPSV\n"
"                              MAIL MDTM MIC MKD MLFL MODE MRCP MRSQ MSAM MSND \n"
"                              MSOM NLST NOOP OPTS PASS PASV PBSZ PORT PROT PWD\n"
"                              QUIT REIN REST RETR RMD RNFR RNTO SITE SIZE SMNT\n"
"                              STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD\n"
"                              XPWD XRCP XRMD XRSQ XSEM XSEN...\n"
"                        SMTP: ATRN AUTH BDAT DATA EHLO ETRN EXPN HELO HELP MAIL\n"
"                              NOOP QUIT RCPT RSET SAML SEND SOML STARTTLS TURN \n"
"                              VRFY...\n"
"                         POP: APOP AUTH CAPA DELE LIST NOOP PASS QUIT RETR RSET\n"
"                              STAT STLS TOP UIDL USER XTND...\n"
"                        NNTP: ARTICLE AUTHINFO CHECK DATE GROUP HELP IHAVE LAST\n"
"                              LIST LISTGROUP MODE_READER MODE_STREAM NEWGROUPS \n"
"                              NEWNEWS NEXT POST QUIT SLAVE TAKETHIS XGTITLE XHDR\n"
"                              XINDEX XINDEX XPAT XPATH XREPLIC XROVER XTHREAD...\n"
"                              (here '_' must be replaced by ' ')\n"
"--match-params     str1[...[[ str2] str3]...]\n"
"                                Match content in command packet\n"
"--match-content    str1[...[[ str2] str3]...]\n"
"                                Match content in stream packet\n"
"--replace-with     string        Replace content with string, only use with --content\n"
"--disable-cookie                Disable set HTTP cookie\n"
"                           0: Not disable cookie\n"
"                           1: Disable cookie\n"
"--disable-encoding              Disable content encoding, such as gzip, deflate\n"
"                           0: Not disable encoding\n"
"                           1: Disable encoding\n"
"--clear-tag                     Clear HTTP tag\n"
"                           0: Not clear tag\n"
"                           1: Clear tag\n"
"--reset                         Disconnect abnormal link\n"
"                           0: Not reset connect\n"
"                           1: Reset connect\n",
NETFILTER_VERSION);

	fputc('\n', stdout);
}

static struct option opts[] = {
	{ "port", 1, 0, '1' },
	{ "match-cmds", 1, 0, '2' },
	{ "match-params", 1, 0, '3' },
	{ "match-content", 1, 0, '4' },
	{ "replace-with", 1, 0, '5' },
	{ "disable-cookie", 1, 0, '6' },
	{ "disable-encoding", 1, 0, '7' },
	{ "clear-tag", 1, 0, '8' },
	{ "reset", 1, 0, '9' },
	{0}
};

static void init(struct ipt_entry_match *m, unsigned int *nfcache)
{
	*nfcache |= NFC_UNKNOWN;
}

static int nibble(char what)
{
	if(isdigit((int)what)) return what - '0';
	if(isxdigit((int)what)) return toupper((int)what) - 'A' + 10;
	return -1;
}

static void parse_string(const unsigned char *s, char string[])
{	
	int last,next,nextnext,p,pnext,len,lastlen;
	unsigned char buf[CHAR_MAX_LEN*2];
	unsigned char *index, *url, *end;

	strcpy(buf + 1, s);
	strcat(buf + 1, " ");
	last = 0;

	index = url = buf + 1;
	end = buf + 1 + strlen(buf + 1);

	while (index < end)
	{
		//merge %XX character
		if ((*index == '%') && (index < end - 2))
		{
			if (isxdigit((int)*(index + 1)) && isxdigit((int)*(index + 2)))
			{
				*url = (nibble(*(index + 1)) << 4) | nibble(*(index + 2));
				index += 3; 
				url++;
			}
			else
			{
				*url = *index;
				url++;
				index++;
			}        
		}
		else if(*index == ' ')
		{
			//save next substring offset
			buf[last] = url - buf;
#ifdef DEBUG
			printf("ZeSA string length: %d\n", buf[last]);
#endif
			last = buf[last];
			if(last > CHAR_MAX_LEN)
				exit_error(PARAMETER_PROBLEM,
						"substring length can not larger than %d", CHAR_MAX_LEN);

			url++;
			index++;
		}
		else
		{
			*url = *index;
			url++;
			index++;
		}
	}
	buf[last] = 0;
	strcpy(string, buf);

	// sort the strings, using method of BUBBLE
	last = 0;
	while((unsigned char)string[last] != 0)
	{
		lastlen = (unsigned char)string[last] - last;
		next = (unsigned char)string[last];
		while((unsigned char)string[next] != 0)
		{
			len = (unsigned char)string[next] - next;
			if(len < lastlen)
			{
				nextnext = (unsigned char)string[next];
				string[next] = (unsigned char)string[last] + len - lastlen;
				p = last;
				while(p < next)
				{
					pnext = (unsigned char)string[p];
					string[p] += len;
					p = pnext;
				}
				bzero(buf, sizeof(buf));
				strncpy(buf, string + next, len);
				strncat(buf, string + last, next - last);
				memcpy(string + last, buf, strlen(buf));
				lastlen = (unsigned char)string[last] - last;
				next = nextnext;
			}
			else next = (unsigned char)string[next];
		}
		last = (unsigned char)string[last];
	}
}

static int parse(int c, char **argv, int invert, unsigned int *flags,
		const struct ipt_entry *entry,
		unsigned int *nfcache,
		struct ipt_entry_match **match)
{
	struct ipt_ZeSA_info *stringinfo = (struct ipt_ZeSA_info *)(*match)->data;

	switch (c) {
		case '1':
			if(strlen(argv[optind - 1]) > 0)
				stringinfo->port = atoi(argv[optind - 1]);
			else
				stringinfo->port = 0;
			break;

		case '2':
			if(strlen(optarg) > 0 && strlen(optarg) < CHAR_MAX_LEN)
			{
				parse_string(optarg, stringinfo->cmd);
				*flags = *flags + 0x01;
			}
			else
				exit_error(PARAMETER_PROBLEM,
						"commands length can not less than 0 or more than %d", CHAR_MAX_LEN);
		
			break;

		case '3':
			if(strlen(argv[optind - 1]) > 0 && strlen(argv[optind - 1]) < CHAR_MAX_LEN)
			{
				parse_string(argv[optind - 1], stringinfo->param);
				*flags = *flags + 0x02;
			}
			else
				exit_error(PARAMETER_PROBLEM,
						"params length can not less than 0 or more than %d", CHAR_MAX_LEN);
			break;
		
		case '4':
			if(strlen(argv[optind - 1]) > 0 && strlen(argv[optind - 1]) < CHAR_MAX_LEN)
			{
				parse_string(argv[optind - 1], stringinfo->content);
				*flags = *flags + 0x04;
			}
			else
				exit_error(PARAMETER_PROBLEM,
						"content length can not less than 0 or more than %d", CHAR_MAX_LEN);
			break;

		case '5':
			if(strlen(argv[optind - 1]) > 0 && strlen(argv[optind - 1]) < CHAR_MAX_LEN)
			{
				strcpy(stringinfo->replace, argv[optind - 1]);
				*flags = *flags + 0x08;
			}
			else
				exit_error(PARAMETER_PROBLEM,
						"replace length can not less than 0 or more than %d", CHAR_MAX_LEN);
			break;

		case '6':
			*flags = *flags + 0x10;
			if(strcmp(argv[optind - 1], "1") == 0)
				stringinfo->cookie = 1;
			else
				stringinfo->cookie = 0;
			break;

		case '7':
			*flags = *flags + 0x20;
			if(strcmp(argv[optind - 1], "1") == 0)
				stringinfo->encoding = 1;
			else
				stringinfo->encoding = 0;
			break;

		case '8':
			if(strcmp(argv[optind - 1], "1") == 0)
				stringinfo->tag = 1;
			else
				stringinfo->tag = 0;
			break;

		case '9':
			if(strcmp(argv[optind - 1], "1") == 0)
				stringinfo->rst = 1;
			else
				stringinfo->rst = 0;
			break;

		default:
			return 0;
	}
	return 1;
}

static void final_check(unsigned int flags)
{
	if (flags != 0x01 && flags != 0x03 && flags != 0x04 && flags != 0x05 && flags != 0x07 && 
			flags != 0x0c && flags != 0x0d && flags != 0x0f && 
			flags != 0x10 && flags != 0x20 && flags != 0x30)
		exit_error(PARAMETER_PROBLEM, "ZeSA match: You must specify 
				'--match-cmds' or 
				'--match-cmds and --match-params' or 
				'--match-content' or 
				'--match-cmds and --match-content' or 
				'--match-cmds --match-params and --match-content' or
				'--match-content and --replace-with' or 
				'--match-cmds --match-content and --replace-with' or 
				'--match-cmds --match-params --match-content and --replace-with' or
				'--disable-cookie' or
				'--disable-encoding' or
				'--disable-cookie and --disable-encoding'");
}

static void
print_parse_string(char * string)
{
	char *needle;
	char str[CHAR_MAX_LEN];
	unsigned char sublen, head, tail;
	
	head = 0;
	while((tail = string[head]))
	{
		needle = string + head + 1;
		sublen = tail - head - 1;
		head = tail;
		
//		if(sublen > 8) sublen = 7;//skip too longer string
		strncpy(str, needle, sublen);
		str[sublen] = 0x00;
		printf("%s ", str);
	}
}

static void print_string(struct ipt_ZeSA_info * info, int numeric)
{
	if(info->port > 0)
		printf("port '%d' ", info->port);

	if(strlen(info->cmd) > 0)
	{
		char *needle;
		char str[CHAR_MAX_LEN];
		unsigned char sublen, head, tail;
		char *string = info->cmd;
	
		printf("match-cmds '");	
		head = 0;
		while((tail = string[head]))
		{
			needle = string + head + 1;
			sublen = tail - head - 1;
			head = tail;
		
//			if(sublen > 8) sublen = 7;
			strncpy(str, needle, sublen);
			str[sublen] = 0x00;
			printf("%s ", str);
		}
		printf("' ");
	
		if(strlen(info->param) > 0)
		{
			printf("match-params '");
			print_parse_string(info->param);
			printf("' ");
		}
	}
		
	if(strlen(info->content) > 0)
	{
		printf("match-content '");
		print_parse_string(info->content);
		printf("' ");
	}

	if(strlen(info->replace) > 0)
		printf("replace-with '%s' ", info->replace);

	if(info->cookie)
		printf("disable-cookie ");

	if(info->encoding)
		printf("disable-encoding ");
	
	if(info->tag)
		printf("clear-tag ");

	if(info->rst)
		printf("enable-reset ");
}

static void print(const struct ipt_ip *ip,
		const struct ipt_entry_match *match,
		int numeric)
{
	printf("ZeSA: ");
	print_string((struct ipt_ZeSA_info *)match->data, numeric);
}

static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match)
{
	print_string((struct ipt_ZeSA_info *)match->data, 0);
}

static struct iptables_match ZeSA = {
	NULL,
	"ZeSA",
	NETFILTER_VERSION,
	IPT_ALIGN(sizeof(struct ipt_ZeSA_info)),
	IPT_ALIGN(sizeof(struct ipt_ZeSA_info)),
	&help,
	&init,
	&parse,
	&final_check,
	&print,
	&save,
	opts
};

void _init(void)
{
	register_match(&ZeSA);
}
